package brainbots;

import java.util.ArrayList;

import ratson.utils.DoubleMatrix;
import brainbots.bot.BotColors;
import brainbots.bot.BrainBot;
import brainbots.brains.Brain;
import brainbots.brains.MatrixBrain;

/**Breeder that controls population of a linear brain brainbots*/
public class MatrixBrainBreeder extends AbstractBreeder {

	private int reproductionEnergy=50;
	private int minimalReproductionEnergy=80;//minimal value of energy, required for starting reproduction;
	private int initialBotEnergy = 80;//how much energy has bot when it was born
	

	@Override
	public void onBotDeath(BrainBot bot) {
		BrainBot[] pair = findRandomPair();
		
		BrainBot childBot=null;
		if (pair != null){
			MatrixBrain childBrain = generateChild((MatrixBrain)pair[0].getBrain(), (MatrixBrain)pair[1].getBrain());
			
			pair[0].decreaseEnergy(reproductionEnergy);
			
			childBot = new BrainBot(pair[0].getX(), pair[0].getY(), Math.random()*2*Math.PI);
			
			if (pair[0].colors != null && pair[1].colors !=null)
				childBot.colors = BotColors.hybride(pair[0].colors, pair[1].colors);
			
			childBot.setBrain(childBrain);
			System.out.println("Child");
		}else{
			//pair not found, creating orphan
			childBot=generateOrphan(); 
			System.out.println("Orphan");
		}
		
		childBot.setEnergy(initialBotEnergy);
		world.addBot(childBot);
	}
	
	private BrainBot[] findRandomPair(){
		ArrayList<BrainBot> suitableBots = new ArrayList<BrainBot>(world.bots.size()/2);
		for (BrainBot b: world.bots){
			if (b.getEnergy() > minimalReproductionEnergy){
				suitableBots.add(b);
			}
		}
		if (suitableBots.size() >= 2){
			int idx1 = (int)(Math.random()*suitableBots.size());
			int idx2 = (int)(Math.random()*suitableBots.size());
			return new BrainBot[] {suitableBots.get(idx1), suitableBots.get(idx2)};
		}else if (suitableBots.size() == 1){
			return new BrainBot[] {suitableBots.get(0), suitableBots.get(0)};
		}else{//0
			return null;
		}
	}

	private BrainBot getRandomBot() {
		int numBots = world.bots.size();
		if (numBots == 0)
			return null;
		
		int idx = (int)Math.floor(Math.random()*numBots);
		BrainBot b = world.bots.get(idx);
		Brain brain = b.getBrain();
		if (brain instanceof MatrixBrain){
			return b;
		}else{
			return null;
		}
	}

	private MatrixBrain getRandomBrain() {
		int numBots = world.bots.size();
		if (numBots == 0)
			return null;
		
		int idx = (int)Math.floor(Math.random()*numBots);
		BrainBot b = world.bots.get(idx);
		Brain brain = b.getBrain();
		if (brain instanceof MatrixBrain){
			return (MatrixBrain) brain;
		}else{
			return null;
		}
	}

	@Override
	public void onIdle() {
		// TODO Auto-generated method stub

	}
	
	private double noiseAmount = 0.1;
	

	private DoubleMatrix mutatedCombination(DoubleMatrix m1, DoubleMatrix m2, double linCombineK, double noiseValue){
		DoubleMatrix M = new DoubleMatrix(m1.getHeight(), m1.getWidth());
		
		for (int y = 0;y<M.getHeight(); ++y){
			for (int x = 0;x<M.getWidth(); ++x){
				double k = linCombineK + noiseValue*(Math.random()-0.5);//coised combination value
				M.set(y, x, m1.get(y, x)*k + m2.get(y, x)*(1-k));
			}
		}
		
		return M;
	}
	
	private MatrixBrain generateChild(MatrixBrain p1, MatrixBrain p2){
		
		double noiseValue = Math.random()*noiseAmount; //decide overall noise amount for this child
		
		double linCombineK = Math.random()*1.2-0.1;// [-0.1...1.1]
		
		//build matrices A1 and A2 for the new brain, using noised linear combination 
		DoubleMatrix A1 = mutatedCombination(p1.A1, p2.A1, linCombineK, noiseValue);
		DoubleMatrix A2 = mutatedCombination(p1.A2, p2.A2, linCombineK, noiseValue);
		
		//create brain
		MatrixBrain child = new MatrixBrain(p1.getSizeU(), p1.getSizeY(), p1.getSizeX(),p1.getSizeV());
		
		child.A1.set(A1);
		child.A2.set(A2);
		
		return child;
	}
	
	private BrainBot generateOrphan(){
    	BrainBot bot = new BrainBot(400,300,0.6);
    	MatrixBrain brain = new MatrixBrain(bot,16,16);
    	brain.randomInit();
    	bot.setBrain(brain);
    	bot.colors = BotColors.random();
    	return bot;
	}

	@Override
	public void onFoodEaten(Food food) {
        //generate new food item
        Food newFood = new Food(Math.random()*world.getWidth()+world.getX0(), 
        		Math.random()*world.getHeight()+world.getY0());
        world.addFood(newFood);
	}

	@Override
	public void onStartSim() {
		
       //add random bots
        for(int i=0;i<100;++i){        	
        	world.addBot(generateOrphan());
        }
        
        //populate with food
        for (int i = 0; i<200; ++i){
            world.addRandomFood();
        }
	}


}
